<?php
/**
 * 模板类
 *
 * Created	  : 2009-07-12
 * Modified   : 2010-06-24
 * @link	  : http://www.phpfo.com
 * @copyright : [PhpFo.Com] (c) 2009-2011
 * @package   : core.Tpl
 * @version   : 0.1.3
 * @author	: Joseph Chen (Jsph.chen@gmail.com)
 */
/**
 * 定义模板用到的配置
 */
//模板定界符
if (false == defined('START_DELIMITER') && false == defined('END_DELIMITER'))
{
	//	define('START_DELIMITER', '{');
	//	define('END_DELIMITER', '}');
	define('START_DELIMITER', '{');
	define('END_DELIMITER', '}');
}

define('HALT_FOR_FILE_NOT_EXIST', 1);

class Tpl
{
	//是否已经初始化过
	private static $is_inited = false;
	//模板文件
	private static $tpl_file = '';
	//模板缓存文件
	private static $tpl_cache_file = '';
	//模板内容
	private static $tpl_contents = '';
	//递归load次数
	public static $load_times = 0;
	// 模板生成周期
	public static $life_time = 1000;
	// 模板变量列表
	public static $vars = array();
	// 是否强制重新缓存
	public static $recache = 0;
	// 当前是否在使用缓存模板
	public static $is_using_cache = 0;

	/**
	 * 初始化
	 */
	public static function init($vars=array())
	{
		self::$vars = array(
			'app_theme'		=> $vars['app_theme'],
			'suffix_tpl'	=> $vars['suffix_tpl'],
			'theme'			=> $vars['theme'],
			'self_dir'		=> $vars['self_dir'],
			'app_cache_tpl'	=> $vars['app_cache_tpl'],
		);
	}

	/**
	 *
	 * @param $tpl_name 模板名 如index,login等
	 * @param $life_time 模板生成缓存文件生存周期 -1表示永不过期,除非模板更新
	 * @param $autoHf 自动添加头部和底部文件模板,如果是,则传递一个数组
	 * @return string 模板路径
	 */
	public static function load($tpl_name=null)
	{
		extract(self::$vars);

		self::$load_times++;

		$str = '';
		if (empty($tpl_name))
		{
			$tpl_name = self::$vars['script_name'];
		}
		$tpl_file = self::$vars['app_theme'].self::$vars['self_dir']
		.$tpl_name.self::$vars['suffix_tpl'];
		if (!is_file($tpl_file))
		{
			self::halt(HALT_FOR_FILE_NOT_EXIST, $tpl_file);
		}
		//获取模板内容
		$str = '';
		if( !($fp = fopen($tpl_file, 'r')) )
		{
			self::halt(HALT_FOR_FILE_NOT_EXIST);
		}
		$filesize = filesize($tpl_file);
		empty($filesize) && $filesize = 1;
		$str = fread($fp, $filesize);
		fclose($fp);
		$theme = empty(self::$vars['theme']) ? '' : self::$vars['theme'].'/';
		$cache_file = self::$vars['app_cache_tpl'].$theme.$tpl_name.'.php';

		clearstatcache();

		if (is_file($cache_file)) {
			$cache_time = filemtime($cache_file);
		} else {
			$cache_time = 0;
		}
		$recache = self::$recache || !empty($_REQUEST['recache_all'])
		|| filemtime($tpl_file) > $cache_time;
		$expire = self::$life_time >= 0 && (time() - $cache_time) > self::$life_time;
		if($GLOBALS['debug'] || !is_file($cache_file) || $expire || $recache)
		{
			self::parse($str, $cache_file);
		} else {
			self::$is_using_cache = 1;
		}

		return $cache_file;
	}

	/**
	 * 生成模板缓存文件
	 * @return boolean
	 */
	public static function make_cache($cache_file, $str)
	{
		$str = trim($str);
		Fso::mkdir(dirname($cache_file));
		Fso::write($cache_file, $str, 'text', 'w', 0);
		return true;
	}

	/**
	 * 解析模板
	 * @param $tpl_file 模板文件
	 * @param $cache_file 缓存文件
	 * @param $life_time
	 * @return boolean
	 */
	public static function parse($str, $cache_file)
	{
		$sd = preg_quote(START_DELIMITER, '-');
		$ed = preg_quote(END_DELIMITER, '-');
		$sdif = START_DELIMITER;
		$edif = END_DELIMITER;
		
		if (false !== strpos($str, '<!--{')) {
			$str = str_replace('<!--{', START_DELIMITER, $str);
			$str = str_replace('}-->', END_DELIMITER, $str);
		}

		// 翻译所有全局变量在函数内使用
		extract($GLOBALS);


		/**********************************
		 * 开始解析模板
		 */

		/**
		 * 处理模板中包含的模板
		 */
		if (false!==strpos($str, $sdif.'template'))
		{
			$reg = "~{$sd}template[\s]+([\w\-\_\./]+){$ed}~is";
			preg_match_all($reg, $str, $tpls);
			$tpls = $tpls[1]; //模板中的模板后面需要重新编译
			$str = preg_replace_callback($reg, 'Tpl::replace_template', $str);
		}
		if (false!==strpos($str, $sdif.'include'))
		{
			$reg = "~{$sd}include[\s]+([\w\-\_\./]+){$ed}~is";
			preg_match_all($reg, $str, $tpls);
			$tpls = $tpls[1]; //模板中的模板后面需要重新编译
			$str = preg_replace_callback($reg, 'Tpl::replace_template', $str);
		}

		/**
		 * 处理模板中的PHP代码
		 */
		if (false!==strpos($str, $sdif.'php'))
		{
			$reg = '~'.$sd."php[\s]+(.+?)".$ed.'~is';
			$replace = "<?php \\1;?>";
			$str = preg_replace($reg, $replace, $str);
		}

		/**
		 * 处理模板中输出字符串到缓存文件中
		 */
		if (false!==strpos($str, $sdif.'output '))
		{
			$reg = '~'.$sd."output[\s]+(.+?)".$ed.'~e';
			$replace = "eval('return \\1;')";
			$str = preg_replace($reg, $replace, $str);
		}

		/**
		 * 处理模板中的全局变量声明代码
		 */
		if (false!==strpos($str, $sdif.'global'))
		{
			$reg = '~'.$sd."global[\s]+(.+?)".$ed.'~is';
			$replace = "<?php global \\1;?>";
			$str = preg_replace($reg, $replace, $str);
		}

		/**
		 * 处理模板中的echo 输出代码
		 */
		if (false!==strpos($str, $sdif.'echo')
		|| false!==strpos($str, $sdif.'?'))
		{
			$reg = '~'.$sd."(?:\?|echo[\s]+)(.+?)".$ed.'~is';
			$replace = "<?php echo \\1;?>";
			$str = preg_replace($reg, $replace, $str);
		}

		/**
		 * 处理模板中的HTML代码
		 */
		if (false!==strpos($str, $sdif.'html'))
		{
			$reg = "~{$sd}html[\s]+(.+?){$ed}~is";
			$replace = "\\1";
			$str = preg_replace($reg, $replace, $str);
		}

		$str = ltrim($str);
		//删除空行中的Tab符和空格
		$str = preg_replace("/([\r\n]+)[\t| ]+$/s", "\\1", $str);

		//处理变量(含数组)
		$reg = "~{$sd}(\\\$[\w][\w]*"  //基本变量名
		."(?:\-\>)*"  //包含类符号
		."(?:\:\:)*"  //包含类符号
		."(?:[\w])*"  //类属性名
		."(?:\[[\S]+?\])*"  //包含数组
		.")$ed~is";
		$str = preg_replace($reg, "<?php if (isset(\\1)) echo \\1;?>", $str);
		//if (isset(${变量})) echo ${变量}的简写方式
		if (false!==strpos($str, $sdif.'isset '))
		{
			$reg = "~{$sd}(?:isset)[\s]+?([\S\s]+?){$ed}~is";
			$replace = "<?php if (isset(\\1)) echo \\1; ?>";
			$str = preg_replace($reg, $replace, $str);
		}

		//模板中的变量名替换为内容输出
		if (false!==strpos($str, $sdif.'replace '))
		{
			$reg = "~{$sd}replace[\s]+(\\\$[a-z_][\w_]*"  //基本变量名
			."(?:\[[\S]+?\])*"  //包含数组
			."){$ed}~ie";
			$replace = 'eval(\'return \\1;\')';
			$str = preg_replace($reg, $replace, $str);
		}

		/**
		 * 处理模板中的 if/else语句
		 */
		//if (condition) {statement} 的简写方式
		if (false!==strpos($str, $sdif.'if '))
		{
			$reg = "~{$sd}if[\s]+(.+?)[\s]+do:(.+?)[\s]*{$ed}~is";
			$replace = "<?php if (\\1) {\\2;\n} ?>";
			$str = preg_replace($reg, $replace, $str);
		}

		if (false!==strpos($str, $sdif.'if '))
		{
			$reg = "~{$sd}(if)[\s]+(.+?)[\s]*{$ed}~is";
			$replace = "<?php \\1 (\\2) { ?>";
			$str = preg_replace($reg, $replace, $str);
		}


		if (false!==strpos($str, $sdif.'elseif '))
		{
			$reg = "~{$sd}(elseif)[\s]+(.+?){$ed}~is";
			$replace = "<?php } \\1 (\\2) { ?>";
			$str = preg_replace($reg, $replace, $str);
		}

		if (false!==strpos($str, $sdif.'else'))
		{
			$reg = "~{$sd}(else)[\s]*{$ed}~is";
			$replace = "<?php } \\1 { ?>";
			$str = preg_replace($reg, $replace, $str);
		}


		/**
		 * 处理模板中的循环
		 */
		if (false!==strpos($str, $sdif.'for '))
		{
			// for循环
			$str = preg_replace(
					"~{$sd}for\s+(\S+)\;\s*(\S+){$ed}~",
					"<?php for(\$i=\\1; \$i<=\\2; \$i++) { ?>",
			$str
			);
		}
		if (false!==strpos($str, $sdif.'loop '))
		{
			// foreach循环
			$str = preg_replace(
					"~{$sd}loop\s+(\S+)\s+(\S+){$ed}~",
					"<?php if(is_array(\\1)) foreach(\\1 as \\2) { ?>",
			$str
			);
			$str = preg_replace(
					"/{$sd}loop\s+(\S+)\s+(\S+)\s+(\S+){$ed}/",
					"\n<?php if(is_array(\\1)) foreach(\\1 as \\2 => \\3) { ?>",
			$str
			);
		}
		/**
		 * 处理流程控制的结束标签
		 */
		if (false!==strpos($str, $sdif.'/if'.$edif))
		{
			$reg = "~{$sd}\/if{$ed}~";
			$replace = "<?php } ?>";
			$str = preg_replace($reg, $replace, $str);
		}
		if (false!==strpos($str, $sdif.'/for'.$edif))
		{
			$reg = "~{$sd}\/for{$ed}~";
			$replace = "<?php } ?>";
			$str = preg_replace($reg, $replace, $str);
		}
		if (false!==strpos($str, $sdif.'/loop'.$edif))
		{
			$reg = "~{$sd}\/loop{$ed}~";
			$replace = "<?php } ?>";
			$str = preg_replace($reg, $replace, $str);
		}


		//处理模板中的常量
		$reg = "~<\!\-\-([A-Z_][A-Z0-9_\.]*)\-\->~";
		$replace = '<?php echo \\1;?>';
		$str = preg_replace($reg, $replace, $str);
		$reg = "~$sd([A-Z_][A-Z0-9_\.]*)$ed~";
		$replace = '<?php if (defined(\'\\1\')) echo \\1;?>';
		$str = preg_replace($reg, $replace, $str);
		/**
		 * 处理模板中的注释,支持如下两种格式,暂时只支持单行注释
		 *  注释内容
		 * /*注释内容* /, 注: *和/无空格
		 * /注释内容/
		 */
		$reg = "~<\!\-\-([\/| )([\*]?)(.+?)(\\2?)(\\1?)\-\->~i";
		$replace = "";
		$str = preg_replace($reg, $replace, $str);

		self::make_cache($cache_file, $str);
		return $str;
	}

	/**
	 * 中断并输出提示内容
	 * @param $reason 中断原因
	 */
	private static function halt($reason=HALT_FOR_FILE_NOT_EXIST, $tpl_file='')
	{
		switch ($reason) {
			case HALT_FOR_FILE_NOT_EXIST:
				//载入公共警告模板
				$msg = sprintf($GLOBALS['LTplFileNotExist'], $tpl_file);
				throw new Exception($msg, E_USER_ERROR);
				break;
					
			default:
				;
				break;
		}
	}

	/**
	 * 设置模板中会用到的变量
	 * @param
	 */
	public static function set_var($var)
	{
		if (is_string($var))
		{
			self::$vars[$var] = $GLOBALS[$var];
		} elseif (is_array($var)) {
			foreach ($var as $v)
			{
				self::$vars[$v] = $GLOBALS[$v];
			}
		}
	}

	/*
	 * 匹配子模板的替换路径函数
	 * @params $matches
	 */
	public static function replace_template($matches)
	{
		$file = $matches[1];
		self::Load($file);
		$theme = empty(self::$vars['theme']) ? '' : self::$vars['theme'].'/';
		$filename = self::$vars['app_cache_tpl'].$theme.$file.'.php';
		$ret = '<?php require_once \''.$filename.'\';?>';
		return $ret;
	}

}



